/************************************************************************
 *   IRC - Internet Relay Chat, ircd/s_ping.c
 *   Copyright (C) 1994 Carlo K ( Run @ undernet.org )
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "patchlevel.h"
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#if defined(__hpux)
#include "inet.h"
#endif
#include <fcntl.h>
#include "sock.h"		  /* If FD_ZERO isn't defined up to this point,  */
			/* define it (BSD4.2 needs this) */
#include "h.h"

#define UPINGBUFSIZE 2000	  /* Lot bigger then 1024, bit smaller then 2048 */
#define UPINGTIMEOUT 120	  /* Timeout waitting for first ping response */


/*
 * sendto_one_notice
 *
 *      sendto_one_notice should be used for all NOTICEs that might be
 *      addressed to a remote user, *and* are from this server
 *
 *  *** MAKE ME LOOK MORE PRETTY:
 *  This really should not be here, but I'm too lazy to go change all
 *  the notice calls below. Seeing as s_uping.c is the only place from
 *  which it's called, and because it should really be removed, I've
 *  put it here; instead of in send.c -TheShadow
 */
void sendto_one_notice (to, pattern, p1, p2, p3, p4, p5, p6, p7, p8)
     char *pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8;
     aClient *to;
{
    char nbuf[1024];

    (void) sprintf (nbuf, "NOTICE %s :", to->name);
    (void) strncat (nbuf, pattern, sizeof (nbuf) - strlen (nbuf));
    sendto_one (to, nbuf, p1, p2, p3, p4, p5, p6, p7, p8);

    return;
}

extern u_long inet_addr ();

void end_ping ();
void cancel_ping ();

/*
 * start_ping
 *
 * As for now, I am abusing the client structure for a ping connection.
 * Used members are:
 * These are used by existing routines as well, and do have their own meaning:
 *   fd       : The socket file descriptor.
 *   status   : To flag that this IS one of these abused ping structures
 *   sockhost : Name of requested server to ping (aconf->host).
 *   name     : aconf->name
 *   ip       : ip#
 * These have more or less their own meaning,
 * but are not used by existing routines:
 *   flags    : To flag that a next ping is requested.
 *   port     : Requested remote port.
 * These are only used by the 'uping' routines
 * and have totally different meanings:
 *   buffer   : buffer hold pingtimes of received packets
 *   confs    : recv/send (char *) buffer.
 *   hopcount : Total number of requested pings
 *   count    : Number of pings left to send.
 *   hashv    : Number of pings left to be received.
 *              NOTE: This is not used by any other part of DreamForge and is
 *                      merely included for UPING to work. This is not the
 *                      ideal idea as we waste memory, albeit very little,
 *                      simply for UPING.  -TheShadow
 *   acpt     : client asking for this ping
 *   lasttime : last time a ping was sent
 *   firsttime: recvfrom timeout
 *   since    : timeout in seconds to next recvfrom
 *   receiveK : minimum in ms
 *   sendM    : average in ms
 *   receiveM : maximum in ms
 */
int start_ping (aClient *cptr)
{
    struct sockaddr_in remote_addr;

    Debug((DEBUG_NOTICE, "start_ping(%x) status %d", cptr, cptr->status));

    if (!(cptr->acpt))
	return -1;

    bcopy ((char *) &cptr->ip, (char *) &remote_addr.sin_addr,
	   sizeof (struct in_addr));
    remote_addr.sin_port = htons (cptr->port);
    remote_addr.sin_family = AF_INET;

    sendto_one_notice (cptr->acpt,
		       "Sending %d ping%s to %s[%s] port %d",
		       cptr->hopcount,
		       (cptr->hopcount == 1) ? "" : "s", cptr->name,
		       inetntoa ((char *) &remote_addr.sin_addr),
		       ntohs (remote_addr.sin_port));

    cptr->firsttime = time (NULL) + UPINGTIMEOUT;
    cptr->since = UPINGTIMEOUT;
    cptr->flags |= (FLAGS_PING);

    return 0;
}

/*
 * send_ping
 *
 */
void send_ping (aClient *cptr)
{
    struct sockaddr_in remote_addr;
    struct timeval tv;

    bcopy ((char *) &cptr->ip, (char *) &remote_addr.sin_addr,
	   sizeof (struct in_addr));
    remote_addr.sin_port = htons (cptr->port);
    remote_addr.sin_family = AF_INET;

    (void) gettimeofday (&tv, NULL);
    (void) sprintf ((char *) cptr->confs, " %10lu%c%6lu",
		    tv.tv_sec, '\0', tv.tv_usec);

    Debug ((DEBUG_SEND, "send_ping: sending [%s %s] to %s.%d on %d",
	    (char *) cptr->confs, (char *) cptr->confs + 12,
	    inetntoa ((char *) &remote_addr.sin_addr),
	    ntohs (remote_addr.sin_port), cptr->fd));

    if (sendto (cptr->fd, (char *) cptr->confs, 1024, 0,
		(struct sockaddr *) &remote_addr,
		sizeof (struct sockaddr_in)) != 1024) {
	if (cptr->acpt)
	    sendto_one_notice (cptr->acpt, "UPING: sendto() failed: %s",
			       strerror (get_sockerr (cptr)));
//    Debug((DEBUG_SEND, "send_ping: sendto failed on %d (%d)", cptr->fd, err));
	(void) end_ping (cptr);
    }
    else if (--(cptr->count) <= 0) {
	ClearPing (cptr);
	if (cptr->hashv <= 0)
	    end_ping (cptr);
    }
    return;
}

/*
 * read_ping
 *
 */
void read_ping (aClient *cptr)
{
    int addr_len = sizeof (struct sockaddr_in);
    struct sockaddr_in remote_addr;
    struct timeval tv;
    int len;
    unsigned long int pingtime;
    char *s;

    bcopy ((char *) &cptr->ip, (char *) &remote_addr.sin_addr,
	   sizeof (struct in_addr));
    remote_addr.sin_port = htons (cptr->port);
    remote_addr.sin_family = AF_INET;

    (void) gettimeofday (&tv, NULL);

    if ((len = recvfrom (cptr->fd, (char *) cptr->confs, UPINGBUFSIZE, 0,
			 (struct sockaddr *) &remote_addr,
			 &addr_len)) == -1) {
	int err = errno;
	sendto_one_notice (cptr->acpt,
			   "UPING: recvfrom: %s",
			   strerror (get_sockerr (cptr)));
	Debug ((DEBUG_SEND, "read_ping: recvfrom: %d", err));
	if (err != EAGAIN)
	    end_ping (cptr);
	return;
    }
    if (len < 19)
	return;			  /* Broken packet */

    pingtime = (tv.tv_sec - atoi ((char *) cptr->confs + 1)) * 1000 +
	(tv.tv_usec -
	 atoi ((char *) cptr->confs + strlen ((char *) cptr->confs) +
	       1)) / 1000;
    cptr->sendM += pingtime;
    if (!(cptr->receiveK) || (cptr->receiveK > pingtime))
	cptr->receiveK = pingtime;
    if (pingtime > cptr->receiveM)
	cptr->receiveM = pingtime;
    /* Wait at most 10 times the average pingtime for the next one: */
    if ((cptr->since =
	 cptr->sendM / (100 * (cptr->hopcount - cptr->hashv + 1))) < 2)
	cptr->since = 2;
    cptr->firsttime = tv.tv_sec + cptr->since;

    Debug (("read_ping: %d bytes, ti %lu: [%s %s] %u ms",
	    len, cptr->since, (char *) cptr->confs,
	    (char *) cptr->confs + strlen ((char *) cptr->confs) + 1,
	    pingtime));

    s = cptr->buffer + strlen (cptr->buffer);
    sprintf (s, " %lu", pingtime);

    if ((--(cptr->hashv) <= 0 && !DoPing (cptr)) || !(cptr->acpt))
	end_ping (cptr);

    return;
}

int ping_server (aClient *cptr, struct hostent *hp)
{
    if ((!cptr->ip.s_addr)) {
	struct hostent *hp;
	char *s;
	Link lin;

	if (!(cptr->acpt))
	    return -1;		  /* Oper left already */

	lin.flags = ASYNC_PING;
	lin.value.cptr = cptr;
	nextdnscheck = 1;
	s = (char *) index (cptr->sockhost, '@');
	s++;			  /* should never be NULL; cptr->sockhost is actually a conf->host */
	if ((cptr->ip.s_addr = inet_addr (s)) == -1) {
	    cptr->ip.s_addr = 0;
	    hp = gethost_byname (s, &lin);
	    Debug ((DEBUG_NOTICE, "ping_sv: hp %x ac %x ho %s", hp, cptr, s));
	    if (!hp)
		return 0;
	    bcopy (hp->h_addr, (char *) &cptr->ip, sizeof (struct in_addr));
	}
    }
    return start_ping (cptr);
}

/*
   ** m_uping  -- by Run
   **
   **   parv[0] = sender prefix
   **   parv[1] = pinged server
   **      parv[2] = port
   **   parv[3] = hunted server
   **      parv[4] = number of requested pings
 */
int m_uping (aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    aConfItem *aconf;
    int fd, opt;
    int port = atoi (UDPPORT);

    if (!IsPrivileged (sptr)) {
	sendto_one (sptr, err_str (ERR_NOPRIVILEGES), me.name, parv[0]);
	return -1;
    }
    if (parc < 2) {
	sendto_one (sptr, err_str (ERR_NEEDMOREPARAMS), me.name, parv[0],
		    "UPING");
	return 0;
    }
    if (MyClient (sptr)) {
	if (parc == 2) {
	    parv[parc++] = UDPPORT;
	    parv[parc++] = me.name;
	    parv[parc++] = "5";
	}
	else if (parc == 3) {
	    if (isdigit(*parv[2])) {
		parv[parc++] = me.name;
	    }
	    else {
		parv[parc++] = parv[2];
		parv[2] = UDPPORT;
	    }
	    parv[parc++] = "5";
	}
	else if (parc == 4) {
	    if (isdigit (*parv[2])) {
		if (isdigit (*parv[3])) {
		    parv[parc++] = parv[3];
		    parv[3] = me.name;
		}
		else
		    parv[parc++] = "5";
	    }
	    else {
		parv[parc++] = parv[3];
		parv[3] = parv[2];
		parv[2] = UDPPORT;
	    }
	}
    }
    if (hunt_server (cptr, sptr, ":%s UPING %s %s %s %s", 3, parc, parv) !=
	HUNTED_ISME)
	return 0;

    if (BadPtr(parv[4]) || atoi (parv[4]) <= 0) {
	sendto_one_notice (sptr, "UPING: Illegal number of packets: %s",
			   parv[4]);
	return 0;
    }
    /* Check if a CONNECT would be possible at all (adapted from m_connect) */
    for (aconf = conf; aconf; aconf = aconf->next)
	if (aconf->status == CONF_CONNECT_SERVER &&
	    match (parv[1], aconf->name) == 0)
	    break;
    if (!aconf)
	for (aconf = conf; aconf; aconf = aconf->next)
	    if (aconf->status == CONF_CONNECT_SERVER &&
		(match (parv[1], aconf->host) == 0 ||
		 match (parv[1], index (aconf->host, '@') + 1) == 0))
		break;
    if (!aconf) {
	sendto_one_notice(sptr, "UPING: Host %s not listed in ircd.conf",
			   parv[1]);
	return 0;
    }
    if (AskedPing(sptr))
	(void) cancel_ping (sptr, sptr);	/* Cancel previous ping request */

    /*
     * Determine port: First user supplied, then default : 7007
     */
    if (!BadPtr(parv[2]) && (port = atoi (parv[2])) <= 0)
	port = atoi (UDPPORT);

    (void) alarm (2);
    if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
	int err = errno;
	(void) alarm (0);
	sendto_ops("m_uping: socket: %s", (err != EAGAIN) ?
		    strerror(err) : "No more sockets");
	sendto_one_notice(sptr, "UPING: Unable to create udp ping socket");
#ifdef	USE_SYSLOG
	syslog (LOG_ERR, "Unable to create udp ping socket");
#endif
	return 0;
    }
    (void)alarm(0);

    if (fcntl (fd, F_SETFL, FNDELAY) == -1) {
	sendto_ops("m_uping: fcntl FNDELAY: %s", strerror (errno));
	sendto_one_notice(sptr, "UPING: Can't set fd non-blocking");
	close(fd);
	return 0;
    }
    /*
       ** On some systems, receive and send buffers must be equal in size.
       ** Others block select() when the buffers are too small
       ** (Linux 1.1.50 blocks when < 2048) --Run
     */
    opt = 2048;
    if (setsockopt
	(fd, SOL_SOCKET, SO_SNDBUF, (OPT_TYPE *) & opt, sizeof (opt))
	< 0
	|| setsockopt (fd, SOL_SOCKET, SO_RCVBUF, (OPT_TYPE *) & opt,
		       sizeof (opt)) < 0) {
	int err = errno;
	sendto_ops("m_uping: setsockopt SO_SNDBUF|SO_RCVBUF: %s",
		    strerror(err));
	sendto_one_notice(sptr, "UPING: error in setsockopt: %s",
			   strerror(err));
	close(fd);
	return 0;
    }
    if (fd >= MAXCONNECTIONS) {
	sendto_ops("Can't allocate fd for uping (all connections in use)");
	sendto_one_notice(sptr, "UPING: All connections in use");
	close(fd);
	return 0;
    }
    if (fd > highest_fd)
	highest_fd = fd;
    local[fd] = cptr = make_client (NULL, NULL);
    cptr->confs = (Link *)MyMalloc(UPINGBUFSIZE);	/* Really a (char *) */
    cptr->fd = fd;
    cptr->port = port;
    cptr->hopcount = cptr->hashv = cptr->count = MIN (20, atoi (parv[4]));
    strcpy(cptr->sockhost, aconf->host);
    cptr->acpt = sptr;
    SetAskedPing(sptr);
    bcopy ((void *) &aconf->ipnum, (void *) &cptr->ip,
	   sizeof(struct in_addr));
    strcpy (cptr->name, aconf->name);
    cptr->firsttime = 0;
    SetPing(cptr);

    switch(ping_server(cptr, NULL)) {
    case 0:
	break;
    case -1:
	del_queries((char *) cptr);
	end_ping(cptr);
	break;
    }
    return 0;
}

void end_ping (register aClient *cptr)
{
    Debug ((DEBUG_DEBUG, "end_ping: %x", cptr));
    if (cptr->acpt) {
	if (cptr->firsttime) {	  /* Started at all ? */
	    if (cptr->hashv != cptr->hopcount) {	/* Received any pings at all ? */
		sendto_one_notice (cptr->acpt, "UPING %s%s", cptr->name,
				   cptr->buffer);
		sendto_one_notice (cptr->acpt,
				   "UPING Stats: sent %d recvd %d ; min/avg/max = %lu/%lu/%lu ms",
				   cptr->hopcount - cptr->count,
				   cptr->hopcount - cptr->hashv,
				   cptr->receiveK,
				   (2 * cptr->sendM + cptr->hopcount -
				    cptr->hashv) / (2 * (cptr->hopcount -
							 cptr->hashv)),
				   cptr->receiveM);
	    }
	    else
		sendto_one_notice (cptr->acpt,
				   "UPING: no response from %s within %d seconds",
				   cptr->name,
				   time (NULL) + cptr->since -
				   cptr->firsttime);
	}
	else
	    sendto_one_notice (cptr->acpt,
			       "UPING: Could not start ping to %s %d",
			       cptr->name, cptr->port);
    }
    (void) close (cptr->fd);
    local[cptr->fd] = NULL;
    if (cptr->acpt)
	ClearAskedPing (cptr->acpt);
    MyFree ((char *) cptr->confs);
    free_client (cptr);
}

void cancel_ping (aClient *sptr, aClient *acptr)
{
    int i;
    aClient *cptr;

    Debug ((DEBUG_DEBUG, "Cancelling uping for %x (%s)", sptr, sptr->name));
    for (i = highest_fd; i >= 0; i--)
	if ((cptr = local[i]) && IsPing (cptr) && cptr->acpt == sptr) {
	    cptr->acpt = acptr;
	    del_queries ((char *) cptr);
	    end_ping (cptr);
	    break;
	}
    ClearAskedPing (sptr);
}
